/*
    Android Asynchronous Http Client
    Copyright (c) 2013 JIXIAOLONG <MicroJixl@gmail.com>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
 */
package com.jxl.netframe;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.http.HttpEntity;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;

import android.text.TextUtils;

import com.jxl.netframe.apachebase.ByteArrayPart;
import com.jxl.netframe.apachebase.FilePart;
import com.jxl.netframe.apachebase.Part;
import com.jxl.netframe.apachebase.StringPart;

/** 
 * @author JiXL
 * @time 2013-6-6 上午10:27:46
 * <br>
 * request packet object.use {@link #build(String) build()} to create a RequestParameter.
 */
public class RequestParameter {
	private static String ENCODING = "UTF-8";
	public static final int POST = 1;
	public static final int GET = 2;
	public static final int PUT = 3;
	public static final int DELETE = 4;
	public static final int NORMAL = 0;

	private String url= null;
	private String idString = null;
	private ConcurrentHashMap<String, String> urlParams = null;
	private ArrayList<Part> miniPart = null;
	private int method = GET;
	private int priority = NORMAL;
	private int id = -1;
	private IOBufferListener ioListener;
	private HttpEntity entity;
	private RequestParameter(){
		urlParams = new ConcurrentHashMap<String, String>();
		miniPart = new ArrayList<Part>();
	}
	/**
	 * create RequestParameter.
	 * @param url
	 * @return
	 */
	public static RequestParameter build(String url){
		return build(url,null);
	}
	/**
	 * create RequestParameter.
	 * @param url
	 * @param source
	 * @return
	 */
	public static RequestParameter build(String url,Map<String, String> source){
		if(!TextUtils.isEmpty(url)){
			RequestParameter rq = new RequestParameter();
			rq.url = url;
			StringBuffer sb = new StringBuffer(rq.url);
			if(source != null){
				for(Map.Entry<String, String> entry : source.entrySet()) {
					rq.put(entry.getKey(), entry.getValue());
					sb.append(entry.getKey()+entry.getValue());
				}
			}
			rq.idString = sb.toString();
			return rq;
		}
		return null;
	}

	/**
	 * set a listener to listen file upload progress.
	 * @param ioListener
	 */
	protected void setIOListener(IOBufferListener ioListener) {
		this.ioListener = ioListener;
	}

	//	public int buildID(){
	//		if(TextUtils.isEmpty(url)){
	//			return -1;
	//		}
	//		StringBuffer sb = new StringBuffer(url);
	//		for(Iterator<Entry<String, String>> iterator = urlParams.entrySet().iterator();iterator.hasNext();){
	//			Entry<String, String> entry = iterator.next();
	//			sb.append(entry.getKey()+entry.getValue());
	//		}
	//		for(Iterator<Entry<String, FileWrapper>> iterator = fileParams.entrySet().iterator();iterator.hasNext();){
	//			Entry<String, FileWrapper> entry = iterator.next();
	//			sb.append(entry.getKey()+entry.getValue().getFileName());
	//		}
	//		return sb.toString().hashCode();
	//	}

	public int allocateID(){
		if(TextUtils.isEmpty(idString)){
			return -1;
		}
		this.id = idString.hashCode();
		return this.id;
	}

	public int getPriority() {
		return priority;
	}

	public void setPriority(int priority) {
		this.priority = priority;
	}

	/**
	 * Adds a key/value string pair to the request.
	 * @param key the key name for the new param.
	 * @param value the value string for the new param.
	 */
	public void put(String key, String value){
		if(key != null && value != null) {
			urlParams.put(key, value);
			idString += key+value;
		}
	}

	/**
	 * Adds a file to the request.
	 * @param key the key name for the new param.
	 * @param file the file to add.
	 */
	public void put(String key, File file) throws FileNotFoundException {
		miniPart.add(new FilePart(key, file,null,HTTP.UTF_8));
		idString += key+file.getName();
	}

	/**
	 * Adds parameters by map
	 * @param key
	 * @param source
	 */
	public void put(String key, Map<String, String> source)  {
		if(key != null && source != null) {
			for(Map.Entry<String, String> entry : source.entrySet()) {
				put(entry.getKey(), entry.getValue());
			}
		}
	}

	/**
	 * Adds an input stream to the request.
	 * @param key the key name for the new param.
	 * @param stream the input stream to add.
	 */
	public void put(String key, InputStream stream) {
		put(key, stream, "file"+System.currentTimeMillis());
	}

	/**
	 * Adds an input stream to the request.
	 * @param key the key name for the new param.
	 * @param stream the input stream to add.
	 * @param fileName the name of the file.
	 */
	public void put(String key, InputStream stream, String fileName) {
		put(key, stream, fileName, null);
	}

	/**
	 * Adds an input stream to the request.
	 * @param key the key name for the new param.
	 * @param stream the input stream to add.
	 * @param fileName the name of the file.
	 * @param contentType the content type of the file, eg. application/json
	 */
	public void put(String key, InputStream stream, String fileName, String contentType) {
		if(key != null && stream != null) {
			try {
				miniPart.add(new ByteArrayPart(key,fileName,InputStreamToByte(stream)));
				idString += key+fileName;
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	private byte[] InputStreamToByte(InputStream is) throws IOException {  
		ByteArrayOutputStream byteArrOut = new ByteArrayOutputStream();  
		byte[] temp = new byte[4096];  
		int len = 0;    
		while ((len = is.read(temp)) != -1) {  
			byteArrOut.write(temp, 0, len);  
		}    
		byteArrOut.flush();    
		byte[] bytes = byteArrOut.toByteArray();    
		return bytes; 
	}

	/**
	 * Removes a parameter from the request.
	 * @param key the key name for the parameter to remove.
	 */
	public void remove(String key){
		String value = null;
		if((value=urlParams.get(key)) != null){
			idString.replace(key+value, "");
			urlParams.remove(key);
		}else if(miniPart.size() > 0){
			for(Iterator<Part> iterator = miniPart.iterator();iterator.hasNext();){
				Part part = iterator.next();
				if(part.getName().equals(key) && part instanceof FilePart){
					idString.replace(key+((FilePart)part).getFileName(), "");
					iterator.remove();
				}
				
			}
		}
	}

	@Override
	public String toString() {
		StringBuilder result = new StringBuilder();
		for(ConcurrentHashMap.Entry<String, String> entry : urlParams.entrySet()) {
			if(result.length() > 0)
				result.append("&");

			result.append(entry.getKey());
			result.append("=");
			result.append(entry.getValue());
		}
		for(Iterator<Part> iterator = miniPart.iterator();iterator.hasNext();){
			Part part = iterator.next();
			result.append(part.getName());
			result.append("=");
			result.append("FILE");
		}
		return result.toString();
	}

	public void setEntity(HttpEntity entity){
		this.entity = entity;
	}

	/**
	 * Returns an HttpEntity containing all request parameters
	 */
	public HttpEntity getEntity() {
		if(entity != null)return entity;

		if(miniPart.size() > 0) {
			ArrayList<Part> list = new ArrayList<Part>();
			//Add string params
			for(ConcurrentHashMap.Entry<String, String> entry : urlParams.entrySet()) {
				list.add(new StringPart(entry.getKey(), entry.getValue(),HTTP.UTF_8));
			}
			// Add file params
			for(Part part : miniPart){
				list.add(part);
			}
			Part[] parts = new Part[list.size()];
			parts = list.toArray(parts);

			ProgressMultipartEntity multipartEntity = new ProgressMultipartEntity(parts, this);

			//			UploadMultipartEntity multipartEntity = new UploadMultipartEntity(this);
			//			// Add string params
			//			for(ConcurrentHashMap.Entry<String, String> entry : urlParams.entrySet()) {
			//				multipartEntity.addPart(entry.getKey(), entry.getValue());
			//			}
			//
			//			// Add file params
			//			int currentIndex = 0;
			//			int lastIndex = fileParams.entrySet().size() - 1;
			//			for(ConcurrentHashMap.Entry<String, FileWrapper> entry : fileParams.entrySet()) {
			//				FileWrapper file = entry.getValue();
			//				if(file.inputStream != null) {
			//					boolean isLast = currentIndex == lastIndex;
			//					if(file.contentType != null) {
			//						multipartEntity.addPart(entry.getKey(), file.getFileName(), file.inputStream, file.contentType, isLast);
			//					} else {
			//						multipartEntity.addPart(entry.getKey(), file.getFileName(), file.inputStream, isLast);
			//					}
			//				}
			//				currentIndex++;
			//			}



			if(ioListener != null){
				multipartEntity.setIOListener(ioListener);
			}
			entity = multipartEntity;
		}else {
			try {
				entity = new UrlEncodedFormEntity(getParamsList(), ENCODING);
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
		}
		return entity;
	}

	public boolean isFile(){
		return miniPart.size() > 0;
	}

	private List<BasicNameValuePair> getParamsList() {
		List<BasicNameValuePair> lparams = new LinkedList<BasicNameValuePair>();
		for(ConcurrentHashMap.Entry<String, String> entry : urlParams.entrySet()) {
			lparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
		}
		return lparams;
	}

	/**
	 * you can allocate an id but you should manager this id by yourself when cancel register callback.
	 * {@link #allocateID() allocateID()} method is advisable.
	 * @param id
	 */
	public void setId(int id) {
		this.id = id;
	}

	public int getId() {
		return id;
	}

	public String getUrl() {
		return url;
	}

	public int getMethod() {
		return method;
	}

	public void setMethod(int method) {
		this.method = method;
	}

	public String getUrlWithQueryString() {
		return urlParams.size()>0?url + "?"+URLEncodedUtils.format(getParamsList(), ENCODING):url;
	}

	//	private class FileWrapper {
	//		private InputStream inputStream;
	//		private String fileName;
	//		private String contentType;
	//
	//		public FileWrapper(InputStream inputStream, String fileName, String contentType) {
	//			this.inputStream = inputStream;
	//			this.fileName = fileName;
	//			this.contentType = contentType;
	//		}
	//
	//		public String getFileName() {
	//			if(fileName != null) {
	//				return fileName;
	//			} else {
	//				return "IOFILE"+System.currentTimeMillis();
	//			}
	//		}
	//	}

	@Override
	public boolean equals(Object o) {
		if(o instanceof RequestParameter){
			return id ==((RequestParameter)o).id;
		}
		return false;
	}
}
